package controllers

import actors.{MissionManager, UpdateMissionActor}
import akka.actor.ActorSystem
import akka.actor.typed.PostStop
import akka.actor.typed.scaladsl.Behaviors
import command.{CommandExec, CommandUtils, MissionUtils}
import dao._
import implicits.BlastTool
import implicits.Implicits._
import org.joda.time.DateTime
import play.api.libs.json.{JsValue, Json}
import play.api.mvc.{AbstractController, ControllerComponents, Results, WebSocket}
import tool.Pojo.{CommandData, MissionData}
import tool.{FormTool, Tool, TypedActorFlow}

import java.io.File
import javax.inject.Inject
import scala.concurrent.ExecutionContext.Implicits.global
import models.Tables._
import org.zeroturnaround.zip.ZipUtil
import shared.VarTool
import utils.Utils

import scala.concurrent.Future

/**
 * Created by yz on 16/9/2021
 */
class CentrifugeController @Inject()(cc: ControllerComponents)(
  implicit val userDao: UserDao,
  implicit val sampleDao: SampleDao,
  implicit val modeDao: ModeDao,
  implicit val missionDao: MissionDao,
  implicit val system: ActorSystem,
) extends AbstractController(cc) with BlastTool {

  val missionManager = akka.actor.typed.ActorSystem(MissionManager(), "missionManager")
  missionManager ! MissionManager.Ask

  def newMissionBefore = Action { implicit request =>
    val missionName = s"${Tool.generateMissionName}"
    Ok(views.html.user.centrifuge.newMission(missionName))
  }

  def missionNameCheck = Action.async { implicit request =>
    val data = FormTool.missionNameForm.bindFromRequest.get
    val userId = Tool.getUserId
    missionDao.selectOptionByMissionName(userId, data.missionName).map { mission =>
      mission match {
        case Some(y) => Ok(Json.obj("valid" -> false))
        case None =>
          Ok(Json.obj("valid" -> true))
      }
    }
  }

  def newMission = Action.async(parse.multipartFormData) { implicit request =>
    val data = FormTool.missionForm.bindFromRequest().get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val fq1File = Tool.fileMoveDir("fq1File", tmpDir)
    val fq2File = Tool.fileMoveDir("fq2File", tmpDir)
    val userId = Tool.getUserId
    val row = MissionRow(0, s"${data.missionName}", "", userId, new DateTime(), None, "preparing", data.threadNum,
      arg = Tool.getArgJson(data))
    missionDao.insert(row).flatMap(_ => missionDao.selectByMissionName(row.userId, row.missionName)).flatMap { mission =>
      val outDir = Tool.getUserMissionDir
      val missionDir = MissionUtils.getMissionDir(mission.id, outDir)
      val (workspaceDir, resultDir) = (missionDir.workspaceDir, missionDir.resultDir)
      val dataDir = Tool.getDataDir(workspaceDir.getParentFile)
      fq1File.fileCopyToDir(dataDir)
      fq2File.fileCopyToDir(dataDir)
      fq1File.moveTo(Tool.getFq1File(workspaceDir))
      fq2File.moveTo(Tool.getFq2File(workspaceDir))
      Utils.t2ConfigFile(workspaceDir, data)
      Tool.deleteDirectory(tmpDir)
      val newMission = mission.copy(state = "wait")
      missionDao.update(newMission).map { x =>
        Ok(Json.obj("valid" -> true))
      }
    }
  }

  def reRunMission = Action.async { implicit request =>
    val missionIdData = FormTool.missionIdForm.bindFromRequest().get
    val data = FormTool.missionForm.bindFromRequest().get
    val userId = Tool.getUserId
    val row = MissionRow(0, s"${data.missionName}", data.missionIntro, userId, new DateTime(), None, "preparing",
      data.threadNum, arg = Tool.getArgJson(data))
    missionDao.insert(row).flatMap(_ => missionDao.selectByMissionName(row.userId, row.missionName)).flatMap { mission =>
      val outDir = Tool.getUserMissionDir
      val missionDir = MissionUtils.getMissionDir(mission.id, outDir)
      val (workspaceDir, resultDir) = (missionDir.workspaceDir, missionDir.resultDir)
      val otherWorkspaceDir = Tool.getMissionWorkspaceDir(missionIdData.missionId)
      Tool.getDataDir(otherWorkspaceDir.getParentFile).dirCopyToDir(workspaceDir.getParentFile)
      Tool.getFq1File(otherWorkspaceDir).fileCopyToDir(workspaceDir)
      Tool.getFq2File(otherWorkspaceDir).fileCopyToDir(workspaceDir)
      Utils.t2ConfigFile(workspaceDir, data)
      val newMission = mission.copy(state = "wait")
      missionDao.update(newMission).map { x =>
        Ok(Json.obj("valid" -> true))
      }
    }
  }

  def missionManageBefore = Action { implicit request =>
    Ok(views.html.user.centrifuge.missionManage())
  }

  def getAllMission = Action.async { implicit request =>
    val userId = Tool.getUserId
    missionDao.selectAll(userId).map { x =>
      Future {
        val missionIds = x.map(_.id.toString)
        val missionDir = Tool.getUserMissionDir
        missionDir.listFiles().filter {
          dir =>
            !missionIds.contains(dir.getName)
        }.foreach(_.deleteQuietly)
      }
      val array = Utils.getArrayByTs(x)
      Ok(Json.toJson(array))
    }
  }

  def getLogContent = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = FormTool.missionIdForm.bindFromRequest().get
      missionDao.selectByMissionId(data.missionId).map {
        mission =>
          val missionIdDir = Tool.getMissionIdDir(data.missionId)
          val logFile = Tool.getLogFile(missionIdDir)
          Ok(Json.obj("str" -> logFile.str))
      }
  }

  def deleteMissions = Action.async {
    implicit request =>
      val data = FormTool.idsForm.bindFromRequest().get
      val ids = data.ids
      missionDao.deletes(ids).map {
        x =>
          Future {
            ids.foreach { id =>
              val workspaceDir = Tool.getMissionWorkspaceDir(id)
              CommandUtils.killPid(workspaceDir)
              val missionIdDir = Tool.getMissionIdDir(id)
              Utils.deleteDirectory(missionIdDir)
            }
          }
          Ok(Json.toJson("success"))
      }
  }

  def updateMissionSocket = WebSocket.accept[JsValue, JsValue] { implicit request =>
    val userId = Tool.getUserId
    TypedActorFlow.actorRef(out => Behaviors.setup[JsValue] { context =>
      val updateMissionActor = context.spawnAnonymous(UpdateMissionActor(userId, out))
      Behaviors.receiveMessage[JsValue] {
        json: JsValue =>
          val info = (json \ "info").as[String]
          info match {
            case "start" =>
              updateMissionActor ! UpdateMissionActor.TryUpdate
          }
          Behaviors.same
      }.receiveSignal {
        case (context, PostStop) =>
          Behaviors.stopped
      }
    })
  }

  def missionResultBefore = Action { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    Ok(views.html.user.centrifuge.missionResult(data.missionId))
  }

  def getMissionById = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { x =>
      val json = x.myAsJson
      Ok(Json.toJson(json))
    }
  }

  def downloadResult = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val missionId = data.missionId
    missionDao.select(missionId).map { mission =>
      val missionIdDir = Tool.getMissionIdDir(mission)
      val resultFile = new File(missionIdDir, s"result.zip")
      val resultDir = new File(missionIdDir, "result")
      ZipUtil.pack(resultDir, resultFile)
      Ok.sendFile(resultFile).withHeaders(
        CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${
          mission.missionName
        }_result.zip"),
        CONTENT_TYPE -> "application/x-download"
      )
    }
  }

  def getFileData = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    missionDao.select(data.missionId).map { mission =>
      val resultDir = Tool.getMissionResultDir(mission)
      val file = new File(resultDir, fileName)
      val (columnNames, array) = {
        val lines = file.txtLines
        val columnNames = lines.head
        val columnIndexs = lines.head.indices.map(_.toString)
        val contents = lines.drop(1)
        val array = contents.map { columns =>
          columnIndexs.zip(columns).toMap
        }
        (columnNames, Json.toJson(array))
      }
      val json = Json.obj("columnNames" -> columnNames, "array" -> array)
      Ok(json)
    }
  }

  def getCpuNum = Action { implicit request =>
    val cpuNum = Tool.availCpu
    Ok(Json.toJson(cpuNum))
  }

  def reRunMissionBefore = Action { implicit request =>
    val data = FormTool.missionIdOptionForm.bindFromRequest().get
    val missionName = s"${Tool.generateMissionName}_reRun"
    Ok(views.html.user.centrifuge.reRunMission(missionName, data.missionId))
  }

  def getAllReRunMissionNames = Action.async { implicit request =>
    val userId = Tool.getUserId
    missionDao.selectAll(userId).map {
      x =>
        val array = x.filter(x => List("success", "error").contains(x.state)).map { mission =>
          Json.obj("text" -> mission.missionName, "id" -> mission.id)
        }
        Ok(Json.toJson(array))
    }
  }

  def getConfig = Action { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val workspaceDir = Tool.getMissionWorkspaceDir(data.missionId)
    val json = Utils.configFile2Json(workspaceDir)
    val missionData = MyCodec[MissionData].decode(json)
    Ok(missionData.myAsJson)
  }

  def downloadData = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = FormTool.missionIdForm.bindFromRequest().get
      val missionId = data.missionId
      missionDao.selectByMissionId(missionId).map {
        mission =>
          val missionIdDir = Tool.getMissionIdDir(missionId)
          val dataDir = new File(missionIdDir, "data")
          val dataFile = new File(missionIdDir, s"data.zip")
          if (!dataFile.exists()) ZipUtil.pack(dataDir, dataFile)
          Ok.sendFile(dataFile).withHeaders(
            CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${mission.missionName}_data.zip"),
            CONTENT_TYPE -> "application/x-download"
          )
      }
  }

  def updateMission = Action.async { implicit request =>
    val missionId = FormTool.missionIdForm.bindFromRequest().get.missionId
    val missionName = FormTool.missionNameForm.bindFromRequest().get.missionName
    missionDao.select(missionId).flatMap { x =>
      val row = x.copy(missionName = missionName)
      missionDao.update(row)
    }.map { x =>
      Ok("success")
    }
  }

  def dataVisual = Action { implicit request =>
    val domain = request.domain
    Results.Redirect(s"http://${domain}:5000")
  }

  def getStatInfo = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.select(data.missionId).map { mission =>
      val resultDir = Tool.getMissionResultDir(mission)
      val file = Tool.getViewFile(resultDir, mission.missionName)
      val json = Json.obj("str" -> file.str)
      Ok(json)
    }
  }

  def downloadStatInfo = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = FormTool.missionIdForm.bindFromRequest().get
      val missionId = data.missionId
      missionDao.selectByMissionId(missionId).map {
        mission =>
          val resultDir = Tool.getMissionResultDir(mission)
          val file = Tool.getViewFile(resultDir, mission.missionName)
          Ok.sendFile(file).withHeaders(
            CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${file.getName}"),
            CONTENT_TYPE -> "application/x-download"
          )
      }
  }

}
